﻿// ***********************************************************************
// Assembly         : SecurityLayer
// Author           : LJO+01 (Laurent Jordi)
// Created          : 06-05-2013
// Last Modified By : LJO+01 (Laurent Jordi)
// Last Modified On : 06-05-2013
// ***********************************************************************
// <copyright file="EFRoleProvider.cs" company="Copyright © Innovacall 2011">
//     Copyright (c) Copyright © Innovacall 2011. All rights reserved.
// </copyright>
// <summary></summary>
// ***********************************************************************
using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Provider;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web.Security;
using InnovaApp.Layers.DataLayer;
using InnovaApp.Layers.DomainLayer;
using InnovaApp.Layers.ResourcesLayer.Properties;

namespace InnovaApp.Layers.SecurityLayer.RoleAndMembershipProvider.Security
{
    /// <summary>
    /// Class EFRoleProvider
    /// </summary>
	class EFRoleProvider : RoleProvider
	{
		#region members
        /// <summary>
        /// The EVENTSOURCE
        /// </summary>
		private const string EVENTSOURCE = "EFRoleProvider";
        /// <summary>
        /// The EVENTLOG
        /// </summary>
		private const string EVENTLOG = "Application";
        /// <summary>
        /// The exception message
        /// </summary>
		private const string exceptionMessage = "";//EF_RoleProvider_ExceptionMessage
        /// <summary>
        /// The connection string
        /// </summary>
		private string connectionString;
        /// <summary>
        /// The _application name
        /// </summary>
		private string _applicationName = _RoleAndMembershipProvider_Base.RootAppName;
		#endregion

		//exceptionMessage = Resources.EF_RoleProvider_ExceptionMessage;
		#region properties

        /// <summary>
        /// Récupère ou définit le nom de l'application, l'application name est l'autorité http c'est à dire le nom de domaine.
        /// Il faut que le application name dans la table SEC.Application soit exactement le nom de domaine avec le sous domaine
        /// Il faut donc ajouter le nom de domaine dans le hosts en dev
        /// </summary>
        /// <value>The name of the application.</value>
        /// <returns>
        /// The name of the application to store and retrieve role information for.
        ///   </returns>
		public override string ApplicationName
		{
			get
			{
				return _RoleAndMembershipProvider_Base.RootAppName; //_applicationName;
			}
			set
			{
				return;
			}
		}

        /// <summary>
        /// Gets or sets a value indicating whether [write exceptions to event log].
        /// </summary>
        /// <value><c>true</c> if [write exceptions to event log]; otherwise, <c>false</c>.</value>
		public bool WriteExceptionsToEventLog { get; set; }
		#endregion

		#region public methods
        /// <summary>
        /// System.Configuration.Provider.ProviderBase.Initialize Method
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
        /// <exception cref="System.ArgumentNullException">config</exception>
        /// <exception cref="System.Configuration.Provider.ProviderException"></exception>
        /// <exception cref="T:System.ArgumentNullException">The name of the provider is null.</exception>
        /// <exception cref="T:System.ArgumentException">The name of the provider has a length of zero.</exception>
        /// <exception cref="T:System.InvalidOperationException">An attempt is made to call <see cref="M:System.Configuration.Provider.ProviderBase.Initialize(System.String,System.Collections.Specialized.NameValueCollection)" /> on a provider after the provider has already been initialized.</exception>
		public override void Initialize(string name, NameValueCollection config)
		{
			// Initialize values from web.config.
			if (config == null)
			{
				throw new ArgumentNullException("config");
			}

			if (String.IsNullOrEmpty(name))
			{
				name = "EFRoleProvider";
			}

			if (String.IsNullOrEmpty(config["description"]))
			{
				config.Remove("description");
				config.Add("description", Resources.EF_RoleProvider_Initialize_Description);//EF_RoleProvider_Initialize_Description
			}

			// Initialize the abstract base class.
			base.Initialize(name, config);

			//ApplicationName = (string)ProviderUtils.GetConfigValue(config, "applicationName", HostingEnvironment.ApplicationVirtualPath);
			//ApplicationName = Membership.ApplicationName;
			//WriteExceptionsToEventLog = (bool)ProviderUtils.GetConfigValue(config, "writeExceptionsToEventLog", false);

			// Read connection string.
			ConnectionStringSettings connectionStringSettings = ConfigurationManager.ConnectionStrings[config["connectionStringName"]];

			if (connectionStringSettings == null || connectionStringSettings.ConnectionString.Trim() == string.Empty)
			{
				throw new ProviderException(Resources.EF_RoleProvider_Initialize_ConnectionStringSettings);//EF_RoleProvider_Initialize_ConnectionStringSettings
			}

			connectionString = connectionStringSettings.ConnectionString;
		}

        /// <summary>
        /// Gets a value indicating whether the specified user is in the specified role for the configured applicationName.
        /// </summary>
        /// <param name="username">The user name to search for.</param>
        /// <param name="roleName">The role to search in.</param>
        /// <returns>true if the specified user is in the specified role for the configured applicationName; otherwise, false.</returns>
		public override bool IsUserInRole(string username, string roleName)
		{
			try
			{
				using (DataRepositories _context = new DataRepositories())
				{
					if (!EFMembershipProvider.CheckUser(username, ApplicationName, _context))
					{
						return false;
					}
					/// LJO : Modification pour que les rôles soient centralisés dans l'application "/"
					// int count = _context.UsersInRoles_Rep.Query(
					//                    UiR =>
					//                        UiR.User.Application.Name == ApplicationName
					//                        &&
					//                        UiR.User.Username == username
					//                        &&
					//                        UiR.Role.Application.Name == ApplicationName
					//                        &&
					//                        UiR.Role.Name == roleName
					//                        ).Count();

					int count = _context.InnoUsersInRoles_Rep.Query(
										UiR =>
											UiR.InnoAppUser.InnoApplication.Name == ApplicationName
											&&
											UiR.InnoAppUser.Username == username
											&&
											UiR.InnoRole.InnoApplication.Name == _RoleAndMembershipProvider_Base.RootAppName
											&&
											UiR.InnoRole.Name == roleName
											).Count();


					return count > 0;
				}
			}
			catch (Exception ex)
			{
				if (WriteExceptionsToEventLog)
				{
					WriteToEventLog(ex, "IsUserInRole");
				}

				throw;
			}
		}

        /// <summary>
        /// Gets a list of the roles that a specified user is in for the configured applicationName.
        /// </summary>
        /// <param name="username">The user to return a list of roles for.</param>
        /// <returns>A string array containing the names of all the roles that the specified user is in for the configured applicationName.</returns>
        /// <exception cref="System.ArgumentNullException">username</exception>
		public override string[] GetRolesForUser(string username)
		{
			string[] toReturn;
			using (DataRepositories _context = new DataRepositories())
			{
				if (!EFMembershipProvider.CheckUser(username, ApplicationName, _context))
				{
					throw new ArgumentNullException("username");
				}
				toReturn = (_context.InnoUsersInRoles_Rep.Query(UiR => UiR.InnoAppUser.InnoApplication.Name == ApplicationName && UiR.InnoAppUser.Username == username)
												   .Select(UiR => UiR.InnoRole.Name)).ToArray();
				return toReturn;
			}
		}

        /// <summary>
        /// Adds a new role to the data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to create.</param>
        /// <exception cref="System.ArgumentException"></exception>
        /// <exception cref="System.Configuration.Provider.ProviderException"></exception>
		public override void CreateRole(string roleName)
		{
			// Validate role name
			if (roleName.Contains(","))
			{
				throw new ArgumentException(Resources.EF_RoleProvider_Initialize_CreateRole_RoleNameException);//EF_RoleProvider_Initialize_CreateRole_RoleNameException
			}

			if (RoleExists(roleName))
			{
				throw new ProviderException(Resources.EF_RoleProvider_Initialize_CreateRole_RoleExistesException);//EF_RoleProvider_Initialize_CreateRole_RoleExistesException
			}

			try
			{
				using (DataRepositories _context = new DataRepositories())
				{
					InnoApplication application = ProviderUtils.EnsureApplication(ApplicationName, _context);

					// Create new role
					InnoRole newRole = new InnoRole
					{
						Id = Guid.NewGuid(),
						Name = roleName,
						InnoApplication = application
					};
					_context.InnoRole_Rep.Add(newRole);
					_context.Save();
				}
			}
			catch (Exception ex)
			{
				if (WriteExceptionsToEventLog)
				{
					WriteToEventLog(ex, "CreateRole");
				}
				else
				{
					throw;
				}
			}
		}

        /// <summary>
        /// Removes a role from the data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to delete.</param>
        /// <param name="throwOnPopulatedRole">If true, throw an exception if <paramref name="roleName" /> has one or more members and do not delete <paramref name="roleName" />.</param>
        /// <returns>true if the role was successfully deleted; otherwise, false.</returns>
        /// <exception cref="System.Configuration.Provider.ProviderException">
        /// </exception>
		public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
		{
			// Validate role
			if (!RoleExists(roleName))
			{
				throw new ProviderException(Resources.EF_RoleProvider_DeleteRole_RoleExistesException);//EF_RoleProvider_DeleteRole_RoleExistesException
			}

			if (throwOnPopulatedRole && GetUsersInRole(roleName).Length > 0)
			{
				throw new ProviderException(Resources.EF_RoleProvider_DeleteRole_PopulatedRoleException);//EF_RoleProvider_DeleteRole_PopulatedRoleException
			}

			using (DataRepositories _context = new DataRepositories())
			{
				InnoRole role = GetRole(r => r.Name == roleName, _context);
				if (role == null)
				{
					return false;
				}

				try
				{
					_context.InnoRole_Rep.Remove(role);
					_context.Save();
				}
				catch (Exception ex)
				{
					if (WriteExceptionsToEventLog)
					{
						WriteToEventLog(ex, "DeleteRole");
						return false;
					}
					throw;
				}
				return true;
			}
		}

        /// <summary>
        /// Gets a value indicating whether the specified role name already exists in the role data source for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to search for in the data source.</param>
        /// <returns>true if the role name already exists in the data source for the configured applicationName; otherwise, false.</returns>
		public override bool RoleExists(string roleName)
		{
			using (DataRepositories _context = new DataRepositories())
			{
				try
				{
					return GetRole(r => r.Name == roleName, _context) != null;
				}
				catch (ProviderException)
				{
					return false;
				}
			}
		}

        /// <summary>
        /// Adds the specified user names to the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="userNames">A string array of user names to be added to the specified roles.</param>
        /// <param name="roleNames">A string array of the role names to add the specified user names to.</param>
        /// <exception cref="System.Configuration.Provider.ProviderException">
        /// </exception>
		public override void AddUsersToRoles(string[] userNames, string[] roleNames)
		{
			using (DataRepositories _context = new DataRepositories())
			{
				IQueryable<InnoRole> roles = _context.InnoRole_Rep.Query(MatchRoleApplication()).AsQueryable()
														  .Where(ProviderUtils.BuildContainsExpression<InnoRole, string>(r => r.Name, roleNames));
				if (roles.Count() != roleNames.Length)
				{
					throw new ProviderException(Resources.EF_RoleProvider_AddusersToRoles_RoleNotFound);//EF_RoleProvider_AddusersToRoles_RoleNotFound
				}
				IQueryable<InnoAppUser> users = _context.InnoAppUser_Rep.Query(MatchUserApplication()).AsQueryable()
														  .Where(ProviderUtils.BuildContainsExpression<InnoAppUser, string>(u => u.Username, userNames));
				if (users.Count() != userNames.Length)
				{
					throw new ProviderException(Resources.EF_RoleProvider_AddusersToRoles_UserFound);//EF_RoleProvider_AddusersToRoles_UserFound
				}
				try
				{
					foreach (InnoAppUser user in users)
					{
						foreach (InnoRole role in roles)
						{
							// Check whether user is already in role
							if (IsUserInRole(user.Username, role.Name))
							{
								throw new ProviderException(string.Format(Resources.EF_RoleProvider_AddusersToRoles_UserISAlready + " '{0}'.", role.Name));//EF_RoleProvider_AddusersToRoles_UserISAlready
							}
							_context.InnoUsersInRoles_Rep.Add(new InnoUsersInRoles() { FK_InnoAppUser = user.Id, FK_InnoRole = role.Id });
						}
					}
					_context.Save();
				}
				catch (Exception ex)
				{
					if (WriteExceptionsToEventLog)
					{
						WriteToEventLog(ex, "AddUsersToRoles");
					}
					else
					{
						throw;
					}
				}
			}
		}

        /// <summary>
        /// Removes the specified user names from the specified roles for the configured applicationName.
        /// </summary>
        /// <param name="userNames">A string array of user names to be removed from the specified roles.</param>
        /// <param name="roleNames">A string array of role names to remove the specified user names from.</param>
        /// <exception cref="System.Configuration.Provider.ProviderException">
        /// </exception>
		public override void RemoveUsersFromRoles(string[] userNames, string[] roleNames)
		{
			using (DataRepositories _context = new DataRepositories())
			{
				IQueryable<InnoRole> roles = _context.InnoRole_Rep.Query(MatchRoleApplication()).AsQueryable()
														  .Where(ProviderUtils.BuildContainsExpression<InnoRole, string>(r => r.Name, roleNames));
				if (roles.Count() != roleNames.Length)
				{
					throw new ProviderException(Resources.EF_RoleProvider_RemoveUsersFromRoles_RoleNotFound);//EF_RoleProvider_RemoveUsersFromRoles_RoleNotFound

				}

				//IQueryable<AppUser> users = _context.User.Include("Role").Where(MatchUserApplication()).Where(ProviderUtils.BuildContainsExpression<User, string>(u => u.Username, userNames));
				IQueryable<InnoAppUser> users = _context.InnoAppUser_Rep.Query(MatchUserApplication()).AsQueryable()
														   .Where(ProviderUtils.BuildContainsExpression<InnoAppUser, string>(u => u.Username, userNames)).AsQueryable();

				if (users.Count() != userNames.Length)
				{
					throw new ProviderException(Resources.EF_RoleProvider_RemoveUsersFromRoles_userNotFound);//EF_RoleProvider_RemoveUsersFromRoles_userNotFound
				}

				try
				{
					foreach (InnoAppUser user in users)
					{
						foreach (InnoRole role in roles)
						{
							/// LJO : Modification pour que les rôles soient centralisés dans l'application "/"
							//UsersInRole joinToRemove = _context.UsersInRoles_Rep.Query(
							//                                                UiR => UiR.Role.Application.Name == ApplicationName
							//                                                &&
							//                                                UiR.Role.Name == role.Name
							//                                                &&
							//                                                UiR.User.Application.Name == ApplicationName
							//                                                &&
							//                                                UiR.User.Username == user.Username
							//                                                ).FirstOrDefault();

							InnoUsersInRoles joinToRemove = _context.InnoUsersInRoles_Rep.Query(
												UiR => UiR.InnoRole.InnoApplication.Name == _RoleAndMembershipProvider_Base.RootAppName
												&&
												UiR.InnoRole.Name == role.Name
												&&
												UiR.InnoAppUser.InnoApplication.Name == ApplicationName
												&&
												UiR.InnoAppUser.Username == user.Username
												).FirstOrDefault();

							if (joinToRemove != null)
							{
								_context.InnoUsersInRoles_Rep.Remove(joinToRemove);

							}
						}
					}
					_context.Save();
				}
				catch (Exception ex)
				{
					if (WriteExceptionsToEventLog)
					{
						WriteToEventLog(ex, "RemoveUsersFromRoles");
					}
					else
					{
						throw;
					}
				}
			}
		}

        /// <summary>
        /// Gets a list of users in the specified role for the configured applicationName.
        /// </summary>
        /// <param name="roleName">The name of the role to get the list of users for.</param>
        /// <returns>A string array containing the names of all the users who are members of the specified role for the configured applicationName.</returns>
        /// <exception cref="System.Configuration.Provider.ProviderException"></exception>
		public override string[] GetUsersInRole(string roleName)
		{
			string[] toReturn;
			using (DataRepositories _context = new DataRepositories())
			{
				InnoRole role = GetRole(r => r.Name == roleName, _context);
				if (role == null)
				{
					throw new ProviderException(Resources.EF_RoleProvider_GetUsersInRole_RoleNotFound);//EF_RoleProvider_GetUsersInRole_RoleNotFound
				}
				/// LJO : Modification pour que les rôles soient centralisés dans l'application "/"
				//toReturn = _context.UsersInRoles_Rep.Query(
				//                                    UiR => UiR.Role.Application.Name == ApplicationName
				//                                    &&
				//                                    UiR.Role.Name == roleName
				//                                    ).Select(UiR => UiR.AppUser.Username).ToArray();
				toReturn = _context.InnoUsersInRoles_Rep.Query(
													UiR => UiR.InnoRole.InnoApplication.Name == _RoleAndMembershipProvider_Base.RootAppName
													&&
													UiR.InnoRole.Name == roleName
													).Select(UiR => UiR.InnoAppUser.Username).ToArray();
				return toReturn;
			}
		}

        /// <summary>
        /// Gets a list of all the roles for the configured applicationName.
        /// </summary>
        /// <returns>A string array containing the names of all the roles stored in the data source for the configured applicationName.</returns>
		public override string[] GetAllRoles()
		{
			using (DataRepositories _context = new DataRepositories())
			{
				return _context.InnoRole_Rep.Query(MatchRoleApplication()).Select(r => r.Name).ToArray();
			}
		}

        /// <summary>
        /// Gets an array of user names in a role where the user name contains the specified user name to match.
        /// </summary>
        /// <param name="roleName">The role to search in.</param>
        /// <param name="usernameToMatch">The user name to search for.</param>
        /// <returns>A string array containing the names of all the users where the user name matches <paramref name="usernameToMatch" />
        /// and the user is a member of the specified role.</returns>
        /// <exception cref="System.Configuration.Provider.ProviderException"></exception>
		public override string[] FindUsersInRole(string roleName, string usernameToMatch)
		{
			string[] toReturn;
			using (DataRepositories _context = new DataRepositories())
			{
				InnoRole role = GetRole(r => r.Name == roleName, _context);
				if (role == null)
				{
					throw new ProviderException(Resources.EF_RoleProvider_FindUsersInRole_RoleNotFound);//EF_RoleProvider_FindUsersInRole_RoleNotFound
				}
				/// LJO : Modification pour que les rôles soient centralisés dans l'application "/"
				//toReturn = _context.UsersInRoles_Rep.Query(
				//                    UiR => UiR.Role.Application.Name == ApplicationName
				//                    &&
				//                    UiR.Role.Name == roleName
				//                    ).Where(UiR => UiR.AppUser.Application.Name == ApplicationName && UiR.AppUser.Username == usernameToMatch)
				//                    .Select(UiR => UiR.AppUser.Username).ToArray();

				toReturn = _context.InnoUsersInRoles_Rep.Query(
									UiR => UiR.InnoRole.InnoApplication.Name == _RoleAndMembershipProvider_Base.RootAppName
									&&
									UiR.InnoRole.Name == roleName
									).Where(UiR => UiR.InnoAppUser.InnoApplication.Name == ApplicationName && UiR.InnoAppUser.Username == usernameToMatch)
									.Select(UiR => UiR.InnoAppUser.Username).ToArray();
				return toReturn;
			}
		}

		#endregion

		#region private methods
        /// <summary>
        /// Get role from database. Throws an error if the role could not be found.
        /// </summary>
        /// <param name="query">The role query.</param>
        /// <param name="_context">The context.</param>
        /// <returns>Found role entity.</returns>
        /// <exception cref="System.Configuration.Provider.ProviderException"></exception>
		private InnoRole GetRole(Expression<Func<InnoRole, bool>> query, DataRepositories _context)
		{
			InnoRole role = _context.InnoRole_Rep.Query(MatchRoleApplication()).AsQueryable()
										 .Where(query).FirstOrDefault();
			if (role == null)
			{
				throw new ProviderException(Resources.EF_ProleProvider_GetRole_RoleNameNotBeFound);
				//EF_ProleProvider_GetRole_RoleNameNotBeFound
			}

			return role;
		}

        /// <summary>
        /// Matches the local application name.
        /// </summary>
        /// <returns>Status whether passed in user matches the application.</returns>
		private Expression<Func<InnoRole, bool>> MatchRoleApplication()
		{
			/// LJO : Modification pour que les rôles soient centralisés dans l'application "/"
			//return role => role.Application.Name == ApplicationName;
			return role => role.InnoApplication.Name == _RoleAndMembershipProvider_Base.RootAppName;
		}

        /// <summary>
        /// Matches the local application name.
        /// </summary>
        /// <returns>Status whether passed in user matches the application.</returns>
		private Expression<Func<InnoAppUser, bool>> MatchUserApplication()
		{
			return user => user.InnoApplication.Name == ApplicationName;
		}

        /// <summary>
        /// Writes exception to event log.
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <param name="action">The action.</param>
        /// <remarks>A helper function that writes exception detail to the event log. Exceptions
        /// are written to the event log as a security measure to avoid private database
        /// details from being returned to the browser. If a method does not return a status
        /// or boolean indicating the action succeeded or failed, a generic exception is also
        /// thrown by the caller.</remarks>
		private static void WriteToEventLog(Exception exception, string action)
		{
			EventLog log = new EventLog { Source = EVENTSOURCE, Log = EVENTLOG };

			StringBuilder message = new StringBuilder();
			message.Append(exceptionMessage + "\n\n");
			message.Append("Action: " + action + "\n\n");
			message.Append("Exception: " + exception);

			log.WriteEntry(message.ToString());
		}
		#endregion
	}
}
